package DensityObjects
{
	import TaskStructure.DensityItem;
	
	import flash.display.MovieClip;
	import flash.display.Shape;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	
	/** And object of this class can be dragged around on screen; however, the speed of dragging is determined
	 * by the mass of the object.
	 */ 
	public class FlickableMC extends MovieClip
	{
		// Density of steel = 7.8
		// Density of plastic = .75
		// Density of balsa = .2
		
		public static const MAX_MASS:Number = 500; 
		private static const CONSTANT_FLICK_DISTANCE:Boolean = true; 
		private static const FLICK_THRESHOLD:Number = 5;
		// variables acting as constants
		private static var X_LIMIT:Number, Y_LIMIT:Number;
		
		// private variables for local use.
		private var _refpoint:Point, _flickDirection:Point;
		private var _fps:Number, _frame=uint;
		private var _flickSpeed:Number;
		private var _densityShape:Shape;
		
		// private variables accessible by getter/setter
		private var _flicking:Boolean = false;
		private var _mouseOut:Boolean = false;
		private function get isFlicking():Boolean{return _flicking;}
		
		private var _mass:Number;
		/** Mass can range from 0 to 1, such that 0 means move at full speed and 1 means unmovable */
		public function get mass():Number{return _mass;}
		public function set mass(m:Number):void{_mass = m;}
		private var _volume:Number;
		/** Volume can range from 0 to 1, such that 0 means no volume, 1 means fills the entire space of the screen */
		public function get volume():Number{return _volume;}
		public function set volume(v:Number):void{_volume = v;}
		private var _friction:Number;
		/** Volume can range from 0 to 1, such that 0 means no friction, 1 means cannot move */
		public function get friction():Number{return _friction;}
		public function set friction(v:Number):void{_friction = v;}
		// density cannot be set directly.  This class corresponds to objects, not materials.
		public function get density():Number{return mass/volume;}
		
		/** Give mass in grams, volume in cm^3 */
		public function FlickableMC(mass:Number=0, volume:Number=0, friction:Number=0)
		{
			// set up volume, mass, density
			this._mass = mass;
			this._volume = volume;
			this._friction = friction;
			this.addEventListener( Event.ADDED_TO_STAGE, handleAdded);
		}
		private function handleAdded (event:Event):void
		{
			this.removeEventListener( Event.ADDED_TO_STAGE, handleAdded);
			X_LIMIT = this.parent.width;
			Y_LIMIT = this.parent.height;
			_fps = this.parent.stage.frameRate;
			setupNewFlick();
		}
		
		/** This method is used to show a density representation of the object.  Darker means denser */
		public function showDensity ():void
		{
			if (_densityShape == null || _densityShape.parent != this)
			{
				_densityShape = new Shape();
				_densityShape.graphics.beginFill(0x000000, density/10);
				_densityShape.graphics.drawRect(0, 0, DensityItem.CM_TO_PX, DensityItem.CM_TO_PX);
				_densityShape.graphics.endFill();
				_densityShape.x = (this.width - _densityShape.width)/2;
				_densityShape.y = (this.height - _densityShape.height)/2;
				this.addChild (_densityShape);
			}
			
		}
		/** Hide density object */
		public function hideDensity ():void
		{
			if (_densityShape != null && _densityShape.parent == this)
			{
				this.removeChild(_densityShape);
			}
		}
		/** This function is used to setup a new flick of the object */
		private function setupNewFlick ():void
		{
			_flicking = false;
			_frame = 0;
			this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
		}
		
		/** When user presses the mouse down this object is flicked on the next move. */
		private function handleMouseDown (event:MouseEvent):void
		{
			this.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
			_mouseOut = false;
			_refpoint = new Point(this.parent.mouseX, this.parent.mouseY);
			
			//this.startDrag();
			// Add a _frame listener that will detect motion and flick
			this.addEventListener (Event.ENTER_FRAME, handleFrame);
			// if mouse is removed from this object, restart
			this.addEventListener( MouseEvent.MOUSE_OUT, handleMouseOut);
		}
		/** If user is no longer over this object then it cannot be flicked.
		 *   Set up a variable so that on the next Frame if we didn't reach threshold, we need to restart */
		private function handleMouseOut (event:Event):void
		{
			this.removeEventListener( MouseEvent.MOUSE_OUT, handleMouseOut);
			_mouseOut = true;
		}
		
		/** When user has pressed mouse down this object can be flicked.
		 * Detect a change in mouse position whose distance exceeds some threshold.  Then move in proportion to dx,dy and mass.
		 */ 
		private function handleFrame (event:Event):void
		{
			_frame++;
			//if (_frame % 2 == 0)
			//{	
				// are we currently flicking?
				if (isFlicking)
				{
					// do we have more to go?
					if (_flickSpeed > 1)
					{
						var newX:Number = this.x + _flickSpeed * Math.cos(_flickDirection.x);
						// re-position this object, but check conditions
						if (newX < 0)
						{
							this.x = 0;
							_flickSpeed = 0;
						} else if (newX > (X_LIMIT - this.width))
						{
							this.x = X_LIMIT - this.width;
							_flickSpeed = 0;
						} else
						{
							this.x = newX;
							_flickSpeed -= friction*FLICK_THRESHOLD;
						}
						
						var newY:Number = this.y - _flickSpeed * Math.sin(_flickDirection.y);
						if (newY < 0)
						{
							this.y = 0;
							_flickSpeed = 0;						
						} else if (newY > (Y_LIMIT - this.height))
						{
							this.y = Y_LIMIT - this.height;
							_flickSpeed = 0;
						} else
						{
							this.y = newY;
							_flickSpeed -= friction*FLICK_THRESHOLD;
						}
						
					} else
					{
						// no, stop moving and setup new flick
						this.removeEventListener(Event.ENTER_FRAME, handleFrame);
						setupNewFlick();
					}
				} else
				{	
					var point:Point = new Point(this.parent.mouseX, this.parent.mouseY);
					var dist:Number = Point.distance(_refpoint, point); 
					// Does change in X, Y exceed threshold 
					if (dist > FLICK_THRESHOLD)
					{
						//trace("FlickableMC distance HIT THRESHOLD", dist, FLICK_THRESHOLD, point.x, _refpoint.x, Math.acos((point.x-_refpoint.x)/dist));
						//this.stopDrag();
						_flicking = true;
						var ndist:Number;
						// update the distance
						if (FlickableMC.CONSTANT_FLICK_DISTANCE)
						{
							ndist = FLICK_THRESHOLD*4;
						} else
						{
							ndist = Math.min(dist, FLICK_THRESHOLD*4)
						}
											
						_flickSpeed = ndist * (MAX_MASS - mass)/MAX_MASS;
						_flickDirection = new Point(Math.acos((point.x-_refpoint.x)/dist), -Math.asin((point.y - _refpoint.y)/dist));
					} else
					{
						// if the mouse has left the building then we need to reset
						if (_mouseOut)
						{
							this.removeEventListener(Event.ENTER_FRAME, handleFrame);
							//this.stopDrag();
							setupNewFlick();
						} else
						{
							_refpoint = point;
						}
					}
				}		
			//}
		}
	}
}